/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023.
 * Description: tpm decoupling crypto implementation
 * Author: zhuyan
 * Create: 2023-10-25
 */
#include <linux/hash_info.h>
#include <linux/kallsyms.h>
#include "ima.h"

int ima_sha1_idx;
int ima_hash_algo_idx;
int ima_extra_slots;

int (*ima_tpm_pcr_read_p)(struct tpm_chip *chip, u32 pcr_idx, struct tpm_digest *digest);
int (*ima_tpm_pcr_extend_p)(struct tpm_chip *chip, u32 pcr_idx, struct tpm_digest *digests);
struct tpm_chip* (*ima_tpm_default_chip)(void);

void ima_init_algo_idx(void)
{
	enum hash_algo algo;
	int i;

	ima_sha1_idx = -1;
	ima_hash_algo_idx = -1;

	for (i = 0; i < NR_BANKS(ima_tpm_chip); i++) {
		algo = ima_tpm_chip->allocated_banks[i].crypto_id;
		if (algo == HASH_ALGO_SHA1)
			ima_sha1_idx = i;

		if (algo == ima_hash_algo)
			ima_hash_algo_idx = i;
	}

	if (ima_sha1_idx < 0) {
		ima_sha1_idx = NR_BANKS(ima_tpm_chip) + ima_extra_slots++;
		if (ima_hash_algo == HASH_ALGO_SHA1)
			ima_hash_algo_idx = ima_sha1_idx;
		}
	if (ima_hash_algo_idx < 0)
		ima_hash_algo_idx = NR_BANKS(ima_tpm_chip) + ima_extra_slots++;
}

int ima_alloc_algo_array(void)
{
	int rc = 0;

	if (!ima_tpm_chip && ima_algo_array)
		return rc;
	/* reallocate ima_algo_array when TPM has been initialized */
	kfree(ima_algo_array);
	ima_algo_array = NULL;

	ima_algo_array = kcalloc(NR_BANKS(ima_tpm_chip) + ima_extra_slots,
				sizeof(struct ima_algo_desc), GFP_KERNEL);
	if (!ima_algo_array)
		rc = -ENOMEM;

	return rc;
}

int ima_set_algo_array(void)
{
	enum hash_algo algo;
	int i;
	int rc = 0;

	for (i = 0; i < NR_BANKS(ima_tpm_chip); i++) {
		algo = ima_tpm_chip->allocated_banks[i].crypto_id;
		ima_algo_array[i].algo = algo;
		/* unknown TPM algorithm */
		if (algo == HASH_ALGO__LAST)
			continue;
		if (algo == ima_hash_algo) {
			ima_algo_array[i].tfm = ima_shash_tfm;
			continue;
		}
		ima_algo_array[i].tfm = ima_alloc_tfm(algo);
		if (IS_ERR(ima_algo_array[i].tfm)) {
			if (algo == HASH_ALGO_SHA1) {
				rc = PTR_ERR(ima_algo_array[i].tfm);
				ima_algo_array[i].tfm = NULL;
				return rc;
			}
			ima_algo_array[i].tfm = NULL;
		}
	}
	return rc;
}

int ima_init_algo_array(void)
{
	int i, rc;

	rc = ima_alloc_algo_array();
	if (rc)
		return rc;

	rc = ima_set_algo_array();
	if (rc)
		goto out;

	if (ima_sha1_idx >= NR_BANKS(ima_tpm_chip)) {
		if (ima_hash_algo == HASH_ALGO_SHA1) {
			ima_algo_array[ima_sha1_idx].tfm = ima_shash_tfm;
		} else {
			ima_algo_array[ima_sha1_idx].tfm =
						ima_alloc_tfm(HASH_ALGO_SHA1);
			if (IS_ERR(ima_algo_array[ima_sha1_idx].tfm)) {
				rc = PTR_ERR(ima_algo_array[ima_sha1_idx].tfm);
				goto out;
			}
		}
		ima_algo_array[ima_sha1_idx].algo = HASH_ALGO_SHA1;
	}
	if (ima_hash_algo_idx >= NR_BANKS(ima_tpm_chip) &&
		ima_hash_algo_idx != ima_sha1_idx) {
		ima_algo_array[ima_hash_algo_idx].tfm = ima_shash_tfm;
		ima_algo_array[ima_hash_algo_idx].algo = ima_hash_algo;
	}
	return rc;
out:
	for (i = 0; i < NR_BANKS(ima_tpm_chip) + ima_extra_slots; i++) {
		if (!ima_algo_array[i].tfm ||
			ima_algo_array[i].tfm == ima_shash_tfm)
			continue;
		crypto_free_shash(ima_algo_array[i].tfm);
	}
	return rc;
}

int __init ima_init_crypto(void)
{
	int rc;

	rc = ima_init_ima_crypto();
	if (rc)
		return rc;

	ima_init_algo_idx();
	rc = ima_init_algo_array();
	if (rc < 0)
		crypto_free_shash(ima_shash_tfm);
	return rc;
}

void ima_validate_tpm(struct file *filp)
{
	struct dentry *dentry = file_dentry(filp);
	int rc;

	if (!dentry_is_ima_policy(dentry))
		return;

	if (ima_tpm_chip == NULL) {
		ima_tpm_pcr_read_p = (int (*)(struct tpm_chip *chip,
				u32 pcr_idx, struct tpm_digest *digest))
			kallsyms_lookup_name("tpm_pcr_read");
		ima_tpm_pcr_extend_p = (int (*)(struct tpm_chip *chip,
				u32 pcr_idx, struct tpm_digest *digests))
			kallsyms_lookup_name("tpm_pcr_extend");
		ima_tpm_default_chip = (struct tpm_chip* (*)(void))
			kallsyms_lookup_name("tpm_default_chip");
		if (!ima_tpm_pcr_read_p || !ima_tpm_pcr_extend_p ||
			!ima_tpm_default_chip) {
			pr_info("IMA: tpm_pcr_read/extend/tpm_default_chip not found\n");
		} else {
			ima_tpm_chip = ima_tpm_default_chip();
			if (!ima_tpm_chip)
				return;
			ima_extra_slots = 0;
			ima_init_algo_idx();
			rc = ima_init_digests();
			if (rc != 0)
				return;
			rc = ima_init_algo_array();
			if (rc >= 0) {
				pr_info("IMA: TPM act %d\n", ima_tpm_chip != NULL);
				ima_add_boot_aggregate();
			}
		}
	}
}

void tpm_decouple_pcr_read(u32 idx, struct tpm_digest *d)
{
	int result = 0;

	if (ima_tpm_pcr_read_p == NULL)
		return;

	result = (int)ima_tpm_pcr_read_p(ima_tpm_chip, idx, d);
	if (result != 0)
		pr_err("Error Communicating to TPM chip %d\n", result);
}
